'''
@since: Jan 2, 2012
@author: Chris Giacofei
@version: 2012.06.27

Currently the script keeps openvpn running and logs server names and connection uptime
to an xml for file later analysis.  Future additions will hopefully include preferential 
server selection based on past performance.  The ability to check the connection speed
of a server while it is running would also be desirable.
'''

import os
import sys
import time
import subprocess
import urllib2
import random


import updater
from logging import write_log, get_time, write_xml, time_convert, current_hour
import serverstats
import constants



 
def check_in():
    '''Returns the external IP address of the computer using http://whatismyip.org
    
    @return: IP address as string or 'IP-Unkown' if IP is not determined within 20 seconds.
    '''
    get_ip = False
    ip_start = time.time()
    
    while get_ip == False:
        try:
            ip = urllib2.urlopen(constants.check_ip_url).read()
            get_ip = True
        except:
            if time.time() - ip_start > 20:
                ip = 'IP-Unkown'
                get_ip = True
            print('retry check ip')
            time.sleep(3)
    return ip
    
def rand_server(slist):
    '''Accepts a URL to a list of servers and returns a random 
    item from the list.
    
    @param slist: The list of server_list to select from
    @type slist: string
    
    @return: A string with the name of the server chosen
    '''
    try:      
        server_list = urllib2.urlopen(constants.LIST).read().split('\n')
        stat_list = serverstats.get_server_scores('stats.xml')
        unused_servers = list(serverstats.compare_lists(server_list, stat_list))
        unused_servers = [x for x in unused_servers if x]
        
        if unused_servers != []:
            print('Selecting random unused server')
            index = random.randrange(0,len(unused_servers))
            server = unused_servers[index]
        else:
            print('Selecting weighted server')
            serverstats.weighted_sample(stat_list, 1)
        
    except:
        server = 'failed'
    
    print(server)
    return server

def restart_program():
    """Restarts the current program.
    Note: this function does not return. Any cleanup action (like
    saving data) must be done before calling this function."""
    
    python = sys.executable
    os.execl(python, python, * sys.argv)
        
def vpn_running(my_ip, duration):
    '''Runs open vpn in a loop until an error or stop contition occurs
    
    @param my_ip: ISP IP address
    @type my_ip: string
    
    @param duration: Length of time (seconds) to run before switching to new server
    @type duration: integer
    
    @return: Returns the start and end times as well as vpn IP address
             and disconnect reason
    '''
    prog = subprocess.Popen(constants.openvpn_cmd, stdout=subprocess.PIPE)

    start = time.time()
    while prog.poll() == None:
        print('Polling OpenVPN')
        time.sleep(30)

        
        vpn_ip = check_in()
        print(vpn_ip)
        # restart if unable to verify IP
        if vpn_ip == 'IP-Unkown':
            print(vpn_ip)
            err = 'Unable to verify IP'
            end = time.time()
            prog.kill()
            break
        
        # Restart if IP address is not hidden
        elif vpn_ip == my_ip:
            print(vpn_ip,my_ip)
            err = 'VPN Not Connected'
            end = time.time()
            prog.kill()
            break
        
        # Check for updates daily at noon and download new scripts if available
        elif current_hour > 12:
            new_version = updater.new_version(constants.REVISION)
            if new_version > 0:
                write_log(get_time() + ' >>> Updating from ' + constants.REVISION + ' to new version.')
                end = time.time()
                err = 'New Version'
                updated = updater.download_new_version()
                
                if updated:
                    constants.set_version(new_version)
                    prog.kill()
                    break

        # Stop service if time limit hit
        elif time.time() - start > duration:
            write_log(get_time() + ' >>> Time limit hit, selecting new server.')
            end = time.time()
            err = 'Server Switch'
            prog.kill()
            break
        else:
            err = 'OpenVPN Terminated'

        print('write to log')
        write_log(get_time() + '\n' +
                  'Connection Active\n' +
                  'IP address is: ' + vpn_ip + '\n' + 
                  'UPTIME: ' + time_convert(end-start) + ' d:h:m:s', 
                  aw='w', 
                  log_file=constants.txt_status)
                    
        time.sleep(270)
    
    print(err)
    return start, end, vpn_ip, err

def main():
    '''
    Main function, starts hma script as subprocess.
    Checks for proper VPN connection every 5 minutes and 
    restarts VPN if IP is same as unmasked IP.
    '''
    print('script started')
    xml_entry = {}
    stay_conn = True
    
    os.system('killall openvpn')
    
    #get the current version of program and record
    xml_entry['version'] = constants.REVISION
    print(get_time() + ' >>> Current Version: Revision ' + constants.REVISION)
    
    # Get current unprotected IP
    my_ip = check_in()
    xml_entry['isp_ip'] = my_ip
    print(get_time() + ' >>> ISP IP address: ' + my_ip)

    # Select a random server
    SERVER = rand_server(constants.LIST).rstrip()
    xml_entry['server'] = SERVER
    print(get_time() + ' >>> Connect to HMA Server: ' + SERVER)

    # Get the client config file for the selected server
    SERVER = SERVER.replace(' ', '%20')
    write_log(urllib2.urlopen(constants.CLCONF + SERVER).read(), aw='w', log_file='client.cfg')
    
    # Continue only if initial IP verification and Server selection were successful
    if my_ip != 'IP-Unkown' and SERVER != 'failed':
        print(get_time() + ' >>> Current IP Address: ' + my_ip)
                        
        # Runs openvpn and returns info if it stops running
        # Selects a new random server every 2 days
        start, end, vpn_ip, err = vpn_running(my_ip, constants.NEW_SERVER)
        
        # Setup information for logging after openvpn stops running
        xml_entry['timestamp'] = get_time()            
        xml_entry['start_time'] = str(start)
        xml_entry['end_time'] = str(end)
        xml_entry['uptime'] = time_convert(end - start)
        xml_entry['vpn_ip'] = vpn_ip
        xml_entry['error'] = err
        
        # Write disconnect reason and uptime to the log text file
        print(get_time() + ' >>> Disconnect: ' + err)
        print(get_time() + ' >>> UPTIME: ' + time_convert(end-start) + ' d:h:m:s')
        
        # Writes connection statistics to XML file
        write_xml(xml_entry, constants.xml_log)
    
    # Output to log if could not select server           
    elif SERVER == 'failed':
        print(get_time() + ' >>> Server selection error.')
    
    # Output to log if could not verify starting IP address
    elif my_ip == 'IP-Unkown':
        print(get_time() + ' >>> Unable to verify ISP IP address')
        my_ip = check_in()
    
    # For now this always evaluates true so script runs continuously
    if stay_conn == True:
        restart_program()
        
if __name__ == "__main__":
    main()